﻿using System;
using System.Diagnostics.Contracts;
using O1.IO;

namespace O1.Data.Traversal
{
    [ContractVerification(true)]
    public static class CellIterator
    {
        [CLSCompliant(false)]
        public static int Iterate(ushort trunkId, byte[] buffer, CellObserver observer)
        {
            // Public contracts are intentionally not exception contracts here to defer to overloaded 
            // method call for release build:
            Contract.Requires(buffer != null);
            Contract.Requires(observer != null);
            Contract.Requires(Sz.Cell.Min <= buffer.Length);

            return Iterate(trunkId, buffer, 0, observer);
        }

        [CLSCompliant(false)]
        public static int Iterate(ushort trunkId, byte[] buffer, int startOffset, CellObserver observer)
        {
            Contract.Requires<ArgumentNullException>(buffer != null);
            Contract.Requires<ArgumentNullException>(observer != null);
            Contract.Requires<ArgumentOutOfRangeException>(startOffset > -1, ExceptionText.OffsetMustBeNonNegative);
            Contract.Requires<ArgumentOutOfRangeException>(startOffset + Sz.Cell.Min <= buffer.Length, ExceptionText.IteratorBufferTooSmall);

            var offset = startOffset;

            observer.OnCellVersion(CellVersion.Read(buffer, offset));
            offset += CellVersion.Size;

            observer.OnBeginVertex(CellAddress.ReadLocalId(trunkId, buffer, offset));

            offset += Sz.Cell.CellLocalId;
            while (offset < buffer.Length)
            {
                Contract.Assume(offset > 1);
                var context = buffer[offset];

                Contract.Assume(context < Markup.Count);
                offset += CellInterpreter.Interpret(context, buffer, offset + 1, observer) + 1;
                if (context == Markup.VertexEnd)
                {
                    break;
                }
            }

            return offset;
        }

        [CLSCompliant(false)]
        public static void Iterate(ushort trunkId, InputAdapter adapter, CellObserver observer)
        {
            Contract.Requires<ArgumentNullException>(adapter != null);
            Contract.Requires<ArgumentNullException>(observer != null);

            var offset = adapter.PopOffset(CellVersion.Size);
            if (offset < 0)
            {
                return;
            }

            Contract.Assert(offset + CellVersion.Size < adapter.SharedBuffer.Length);
            observer.OnCellVersion(CellVersion.Read(adapter.SharedBuffer, offset));

            offset = adapter.PopOffset(Sz.CLR.Int32);
            if (offset < 0)
            {
                return;
            }

            Contract.Assert(offset + Sz.CLR.Int32 < adapter.SharedBuffer.Length);
            observer.OnBeginVertex(CellAddress.ReadLocalId(trunkId, adapter.SharedBuffer, offset));
            
            while (!adapter.EndOfStream)
            {
                offset = adapter.PopOffset(1);
                if (offset > -1)
                {
                    Contract.Assume(offset > 1);
                    var context = adapter.SharedBuffer[offset];

                    Contract.Assume(context < Markup.Count);
                    CellInterpreter.Interpret(context, adapter, observer);
                    if (context == Markup.VertexEnd)
                    {
                        break;
                    }
                }
            }
        }
    }
}